/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ohosannotations.helper;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.IJStatement;
import com.helger.jcodemodel.JExpr;
import com.helger.jcodemodel.JMethod;

import org.ohosannotations.OhosAnnotationsEnvironment;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;

import static org.ohosannotations.helper.CanonicalNameConstants.CHAR_SEQUENCE;
import static org.ohosannotations.helper.CanonicalNameConstants.PACMAP;
import static org.ohosannotations.helper.CanonicalNameConstants.STRING;

/**
 * BundleHelper
 *
 * @author dev
 * @since 2021-07-21
 */
public class BundleHelper {
    /**
     * 方法通过类型名称后缀
     */
    public static final Map<String, String> METHOD_SUFFIX_BY_TYPE_NAME = new HashMap<>();

    static {
        METHOD_SUFFIX_BY_TYPE_NAME.put(PACMAP, "Bundle");

        METHOD_SUFFIX_BY_TYPE_NAME.put("boolean", "Boolean");
        METHOD_SUFFIX_BY_TYPE_NAME.put("boolean[]", "BooleanArray");

        METHOD_SUFFIX_BY_TYPE_NAME.put("byte", "Byte");
        METHOD_SUFFIX_BY_TYPE_NAME.put("byte[]", "ByteArray");

        METHOD_SUFFIX_BY_TYPE_NAME.put("char", "Char");
        METHOD_SUFFIX_BY_TYPE_NAME.put("char[]", "CharArray");

        METHOD_SUFFIX_BY_TYPE_NAME.put(CHAR_SEQUENCE, "CharSequence");
        METHOD_SUFFIX_BY_TYPE_NAME.put(CHAR_SEQUENCE + "[]", "CharSequenceArray");
        METHOD_SUFFIX_BY_TYPE_NAME.put("java.util.ArrayList<" + CHAR_SEQUENCE + ">", "CharSequenceArrayList");

        METHOD_SUFFIX_BY_TYPE_NAME.put("double", "Double");
        METHOD_SUFFIX_BY_TYPE_NAME.put("double[]", "DoubleArray");

        METHOD_SUFFIX_BY_TYPE_NAME.put("float", "Float");
        METHOD_SUFFIX_BY_TYPE_NAME.put("float[]", "FloatArray");

        METHOD_SUFFIX_BY_TYPE_NAME.put("int", "Int");
        METHOD_SUFFIX_BY_TYPE_NAME.put("int[]", "IntArray");
        METHOD_SUFFIX_BY_TYPE_NAME.put("java.util.ArrayList<java.lang.Integer>", "IntegerArrayList");

        METHOD_SUFFIX_BY_TYPE_NAME.put("long", "Long");
        METHOD_SUFFIX_BY_TYPE_NAME.put("long[]", "LongArray");

        METHOD_SUFFIX_BY_TYPE_NAME.put("short", "Short");
        METHOD_SUFFIX_BY_TYPE_NAME.put("short[]", "ShortArray");

        METHOD_SUFFIX_BY_TYPE_NAME.put(STRING, "String");
        METHOD_SUFFIX_BY_TYPE_NAME.put("java.lang.String[]", "StringArray");
        METHOD_SUFFIX_BY_TYPE_NAME.put("java.util.ArrayList<java.lang.String>", "StringArrayList");
    }

    private OhosAnnotationsEnvironment environment;
    private AnnotationHelper annotationHelper;
    private APTCodeModelHelper codeModelHelper;
    private boolean restoreCallNeedCastStatement = false;
    private boolean restoreCallNeedsSuppressWarning = false;
    private boolean parcelerBean = false;
    private String methodNameToSave;
    private String methodNameToRestore;
    private TypeMirror upperBound;

    /**
     * BundleHelper
     *
     * @param environment 环境
     * @param element 元素
     */
    public BundleHelper(OhosAnnotationsEnvironment environment, TypeMirror element) {
        this.environment = environment;
        annotationHelper = new AnnotationHelper(environment);
        codeModelHelper = new APTCodeModelHelper(environment);

        String typeString = element.toString();
        TypeMirror type = element;

        if (METHOD_SUFFIX_BY_TYPE_NAME.containsKey(typeString)) {
            methodNameToSave = "put" + METHOD_SUFFIX_BY_TYPE_NAME.get(typeString);
            methodNameToRestore = "get" + METHOD_SUFFIX_BY_TYPE_NAME.get(typeString);
        } else if (element.getKind() == TypeKind.ARRAY) {
            ArrayType arrayType = (ArrayType) element;
            boolean hasTypeArguments = false;
            if (arrayType.getComponentType() instanceof DeclaredType) {
                DeclaredType declaredType = (DeclaredType) arrayType.getComponentType();
                type = declaredType;
                hasTypeArguments = declaredType.getTypeArguments().size() > 0;
            } else if (arrayType.getComponentType().getKind() == TypeKind.TYPEVAR) {
                type = arrayType.getComponentType();
                upperBound = getUpperBound(type);
                restoreCallNeedCastStatement = true;
                restoreCallNeedsSuppressWarning = true;
            } else {
                type = arrayType.getComponentType();
            }

            if (isTypeParcelable(type)) {
                methodNameToSave = "put" + "ParcelableArray";
                methodNameToRestore = "get" + "ParcelableArray";
                if (hasTypeArguments) {
                    restoreCallNeedsSuppressWarning = true;
                }
            } else {
                methodNameToSave = "put" + "Serializable";
                methodNameToRestore = "get" + "Serializable";
                restoreCallNeedCastStatement = true;
            }
        } else if (typeString.startsWith(CanonicalNameConstants.ARRAYLIST)) {
            boolean hasTypeArguments = false;
            if (element instanceof DeclaredType) {
                DeclaredType declaredType = (DeclaredType) element;
                List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
                if (typeArguments.size() == 1) {
                    TypeMirror typeArgument = typeArguments.get(0);
                    if (typeArgument instanceof DeclaredType) {
                        declaredType = (DeclaredType) typeArgument;
                        type = declaredType;
                        hasTypeArguments = declaredType.getTypeArguments().size() > 0;
                    }
                    if (isTypeParcelable(type)) {
                        methodNameToSave = "put" + "ParcelableArrayList";
                        methodNameToRestore = "get" + "ParcelableArrayList";
                        if (hasTypeArguments) {
                            restoreCallNeedsSuppressWarning = true;
                        }
                    }
                }
            }

            if (methodNameToSave == null) {
                methodNameToSave = "put" + "Serializable";
                methodNameToRestore = "get" + "Serializable";
                restoreCallNeedCastStatement = true;
                restoreCallNeedsSuppressWarning = true;
            }
        } else if (typeString.startsWith(CanonicalNameConstants.SPARSE_ARRAY)) {
            methodNameToSave = "put" + "SparseParcelableArray";
            methodNameToRestore = "get" + "SparseParcelableArray";
        } else {
            boolean hasTypeArguments = element.getKind() == TypeKind.DECLARED && hasTypeArguments(element) || //
                element.getKind() == TypeKind.TYPEVAR && hasTypeArguments(getUpperBound(element));
            ParcelerHelper parcelerHelper = new ParcelerHelper(environment);
            if (isTypeParcelable(type)) {
                methodNameToSave = "put" + "Parcelable";
                methodNameToRestore = "get" + "Parcelable";
            } else if (parcelerHelper.isParcelType(type)) {
                methodNameToSave = "put" + "Parcelable";
                methodNameToRestore = "get" + "Parcelable";
                parcelerBean = true;
            } else {
                methodNameToSave = "put" + "Serializable";
                methodNameToRestore = "get" + "Serializable";
                restoreCallNeedCastStatement = true;
                if (hasTypeArguments) {
                    restoreCallNeedsSuppressWarning = true;
                }
            }
        }
    }

    /**
     * 是类型parcelable
     *
     * @param typeMirror 类型的镜子
     * @return boolean
     */
    private boolean isTypeParcelable(TypeMirror typeMirror) {
        TypeMirror parcelableType = annotationHelper
            .typeElementFromQualifiedName(CanonicalNameConstants.SEQUENCEABLE).asType();
        return annotationHelper.isSubtype(typeMirror, parcelableType);
    }

    /**
     * 得到的上界
     *
     * @param type 类型
     * @return {@link TypeMirror}
     */
    private TypeMirror getUpperBound(TypeMirror type) {
        TypeVariable typeVariable = (TypeVariable) type;
        return typeVariable.getUpperBound();
    }

    /**
     * 有类型参数
     *
     * @param type 类型
     * @return boolean
     */
    private boolean hasTypeArguments(TypeMirror type) {
        DeclaredType declaredType = (DeclaredType) type;
        return declaredType.getTypeArguments().size() > 0;
    }

    /**
     * 从包得到表达式来恢复
     *
     * @param variableClass 变量类
     * @param bundle 包
     * @param extraKey 额外的关键
     * @param method 方法
     * @return {@link IJExpression}
     */
    public IJExpression getExpressionToRestoreFromBundle(AbstractJClass variableClass,
        IJExpression bundle, IJExpression extraKey, JMethod method) {
        IJExpression expressionToRestore;
        if ("getParcelableArray".equals(methodNameToRestore)) {
            AbstractJClass erasure;
            if (upperBound != null) {
                erasure = codeModelHelper.typeMirrorToJClass(upperBound).erasure().array();
            } else {
                erasure = variableClass.elementType().erasure().array();
            }
            expressionToRestore = environment.getJClass(org.ohosannotations.api
                .bundle.BundleHelper.class).staticInvoke("getParcelableArray")
                .arg(bundle).arg(extraKey).arg(erasure.dotclass());
        } else {
            expressionToRestore = JExpr.invoke(bundle, methodNameToRestore).arg(extraKey);
        }

        if (parcelerBean) {
            expressionToRestore = environment
                .getJClass(CanonicalNameConstants.PARCELS_UTILITY_CLASS)
                .staticInvoke("unwrap").arg(expressionToRestore);
        }

        if (restoreCallNeedCastStatement) {
            expressionToRestore = JExpr.cast(variableClass, expressionToRestore);

            if (restoreCallNeedsSuppressWarning) {
                codeModelHelper.addSuppressWarnings(method, "unchecked");
            }
        }
        return expressionToRestore;
    }

    /**
     * 从包得到表达
     *
     * @param variableClass 变量类
     * @param bundle 包
     * @param extraKey 额外的关键
     * @param method 方法
     * @return {@link IJExpression}
     */
    public IJExpression getExpressionFromBundle(AbstractJClass variableClass,
        IJExpression bundle, IJExpression extraKey, JMethod method) {
        IJExpression expressionToRestore;
        expressionToRestore = JExpr.invoke(bundle, "getParam").arg(extraKey);
        expressionToRestore = JExpr.cast(variableClass, expressionToRestore);

        return expressionToRestore;
    }

    /**
     * 从现场获得表达式保存
     *
     * @param saveStateBundleParam 保存状态包参数
     * @param fieldName 字段名
     * @param variableRef 变量裁判
     * @return {@link IJStatement}
     */
    public IJStatement getExpressionToSaveFromField(IJExpression saveStateBundleParam,
        IJExpression fieldName, IJExpression variableRef) {
        IJExpression refExpression = variableRef;
        if (parcelerBean) {
            refExpression = environment
                .getJClass(CanonicalNameConstants.PARCELS_UTILITY_CLASS)
                .staticInvoke("wrap").arg(refExpression);
        }
        return JExpr.invoke(saveStateBundleParam, methodNameToSave)
            .arg(fieldName).arg(refExpression);
    }
}
